package br.pucrio.inf.lac.konoha.hyuga.listeners;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Logger;

import br.pucrio.inf.lac.konoha.hyuga.common.Grid;
import br.pucrio.inf.lac.konoha.hyuga.common.Pair;
import br.pucrio.inf.lac.konoha.hyuga.core.DDSLink;
import br.pucrio.inf.lac.konoha.hyuga.events.grid.GridLogicalClusterEvent;
import br.pucrio.inf.lac.konoha.hyuga.events.semantics.DispersedGridClusterEvent;
import br.pucrio.inf.lac.konoha.hyuga.events.semantics.UpdateGridClusterEvent;

import com.espertech.esper.client.EPOnDemandPreparedQueryParameterized;
import com.espertech.esper.client.EPRuntime;
import com.espertech.esper.client.EPServiceProvider;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ClusterDispersionListener  {

	/** DDSLink (Data Stream). */
	private DDSLink ddsLink;
	
	/** CEP Engine. */
	private EPServiceProvider cepEngine;

	// JSON Mapper
	ObjectMapper mapper;
	
    /** LOGGER. */
    private static Logger LOGGER = Logger.getLogger(ClusterDispersionListener.class.getCanonicalName());

	/**
	 * Default Constructor. Receive the DDSLink and EPServiceProvider to send updates to management apps and update clusters.
	 * @param ddsLink - DDSLink
	 */
	public ClusterDispersionListener(DDSLink ddsLink, EPServiceProvider cepEngine) {
		this.ddsLink   = ddsLink;
		this.cepEngine = cepEngine;
		this.mapper    = new ObjectMapper();
	}

	public synchronized void update(DispersedGridClusterEvent disperse) {
	    int x = disperse.getX();
	    int y = disperse.getY();
	    GridLogicalClusterEvent cluster = disperse.getCluster();
	    
	    update(x, y, cluster);
	}
	
	public synchronized void update(int x, int y, GridLogicalClusterEvent cluster) {
	    try {
    		// Removing dispersed cell
            Set<Pair<Integer, Integer>> coreCells = new HashSet<>(cluster.getCoreCells());
            coreCells.remove(new Pair<Integer,Integer>(x, y));
    
            if (coreCells.size() != 0) {
    		    // Remainder Clusters
    	        ArrayList<Set<Pair<Integer, Integer>>> remainderlist = new ArrayList<Set<Pair<Integer, Integer>>>();
    	        Set<Pair<Integer, Integer>> remainderCells = coreCells;
    	        
    	        for (Pair<Integer, Integer> cell : remainderCells) {
    	            int cellx = cell.getFirst();
    	            int celly = cell.getSecond();
    	            boolean neighbor = false;
    	            
    	            for (Set<Pair<Integer, Integer>> remainderCluster : remainderlist) {
    	                for (Pair<Integer, Integer> clusterCell : remainderCluster) {
    	                    if (isNeighbor(cellx, celly, clusterCell.getFirst(), clusterCell.getSecond())) {
                                neighbor = true;
                                break;
                            }
    	                }
    	                
    	                if (neighbor) {
    						remainderCluster.add(cell);
    						break;
    					}
    				}
    	            
    				if (!neighbor) {
    				    Set<Pair<Integer, Integer>> s = new HashSet<Pair<Integer,Integer>>();
    					s.add(cell);
    					remainderlist.add(s);
    				}
    			}
    			
    			EPRuntime cepRT = cepEngine.getEPRuntime();
                EPOnDemandPreparedQueryParameterized insertClusterQuery = cepRT.prepareQueryWithParameters("INSERT INTO Clusters select ?");
    
    			for (Set<Pair<Integer, Integer>> remainderCluster : remainderlist) {
    			    if (remainderCluster == null) continue;
    			    
    				GridLogicalClusterEvent newcluster = new GridLogicalClusterEvent(remainderCluster);
    				insertClusterQuery.setObject(1, newcluster);
    				cepRT.executeQuery(insertClusterQuery);
    				cepRT.route(new UpdateGridClusterEvent(remainderCluster));
    			}
    		}
	    } catch (Exception ex) {
	        LOGGER.error("ERROR AT DISPERSE ROUTINE " + ex.toString());
	    }
    }
	
	public boolean isNeighbor(int x1, int y1, int x2, int y2) {
		boolean neighbor = false;
		if (((Math.abs(x1 - x2) <= 1) && (Math.abs(y1 - y2) <= 1)) &&
		    ((Math.abs(x1 - x2) != 0) || (Math.abs(y1 - y2) != 0))) {
			neighbor = true;
		}
		return neighbor;
	}
}
